package com.dhcc.core.framework.lucene;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;

import javax.annotation.PostConstruct;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.Fragmenter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.search.highlight.SimpleSpanFragmenter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.NIOFSDirectory;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.wltea.analyzer.lucene.IKAnalyzer;

import com.baomidou.mybatisplus.plugins.Page;
import com.google.common.collect.Lists;
import com.dhcc.core.config.properties.SysProperties;
import com.dhcc.core.framework.exception.BizException;
import com.dhcc.core.framework.lucene.util.DocumentUtil;
import com.dhcc.core.framework.lucene.util.IndexObject;
import com.dhcc.core.framework.util.CommonUtil;

/**
 * lucene
 * 
 * @ClassName: LuceneSearcher
 * @Description: TODO
 * @author: cyf
 * @date: 2018年3月22日 下午7:50:01
 */
@Component
public class LuceneSearcher implements ISearcher {

    private final static Logger log = LoggerFactory.getLogger(LuceneSearcher.class);

    private static Analyzer analyzer = null;// 分词器
    private static Directory directory = null;

    @Autowired
    SysProperties sysProperties;

    @PostConstruct
    public void init() {
        try {
            File indexDir = new File(sysProperties.getSysHome() + "lucene");
            if (!indexDir.exists()) {
                indexDir.mkdirs();
            }
            directory = NIOFSDirectory.open(indexDir);
            analyzer = new IKAnalyzer();
        } catch (IOException e) {
            log.error("init lucene path error", e);
        }
    }

    /**
     * 创建索引
     */
    @Override
    public void create(IndexObject indexObject) {

        IndexWriter indexWriter = null;
        try {
            IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_47, analyzer);
            indexWriter = new IndexWriter(directory, config);
            indexWriter.addDocument(DocumentUtil.IndexObject2Document(indexObject));
            indexWriter.commit();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                indexWriter.rollback();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        } finally {
            try {
                indexWriter.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    /**
     * 删除索引
     */
    @Override
    public void delete(String indexObjId) {
        IndexWriter indexWriter = null;
        try {
            IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_47, analyzer);
            indexWriter = new IndexWriter(directory, config);
            indexWriter.deleteDocuments(new Term("id", indexObjId));
        } catch (Exception e) {
            e.printStackTrace();
            try {
                indexWriter.rollback();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        } finally {
            try {
                indexWriter.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    /**
     * 删除索引
     */
    @Override
    public void deleteAll() {
        IndexWriter indexWriter = null;
        try {
            IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_47, analyzer);
            indexWriter = new IndexWriter(directory, config);
            indexWriter.deleteAll();
            /* 清空回收站 */
            indexWriter.forceMergeDeletes();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                indexWriter.rollback();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        } finally {
            try {
                indexWriter.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    /**
     * 更新单挑索引
     */
    @Override
    public void update(IndexObject indexObject) {

        IndexWriter indexWriter = null;

        try {
            IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_47, analyzer);
            indexWriter = new IndexWriter(directory, config);
            indexWriter.updateDocument(new Term("id", indexObject.getId()),
                    DocumentUtil.IndexObject2Document(indexObject));
        } catch (Exception e) {
            e.printStackTrace();
            try {
                indexWriter.rollback();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        } finally {
            try {
                indexWriter.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    /**
     * 查询索引
     */
    @Override
    public Page<IndexObject> search(Integer pageNumber, Integer pageSize, String keyword,String siteId) {

        IndexReader indexReader = null;
        Page<IndexObject> page = null;
        List<IndexObject> searchResults = Lists.newArrayList();
        try {
            indexReader = DirectoryReader.open(directory);
            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
            Query query = getQuery(keyword,siteId);
            ScoreDoc lastScoreDoc = getLastScoreDoc(pageNumber, pageSize, query, indexSearcher);
            /* 将上一页的最后一个document传递给searchAfter方法以得到下一页的结果 */
            TopDocs topDocs = indexSearcher.searchAfter(lastScoreDoc, query, pageSize);
            Highlighter highlighter = addStringHighlighter(query);
            log.info("搜索词语：{}", keyword);
            log.info("总共的查询结果：{}", topDocs.totalHits);
            for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
                int docID = scoreDoc.doc;
                float score = scoreDoc.score;
                Document document = indexSearcher.doc(docID);
                IndexObject indexObject = DocumentUtil.document2IndexObject(analyzer, highlighter, document, score);
                searchResults.add(indexObject);
                log.info("相关度得分：" + score);
            }
            page = new Page<>();
            page.setCurrent(pageNumber);
            page.setTotal(topDocs.totalHits);
            Collections.sort(searchResults);
            page.setRecords(searchResults);
        } catch (Exception e) {
            e.printStackTrace();
            if(indexReader==null){
                throw new BizException("全文检索未创建索引！");
            }
            try {
                indexReader.close();
            } catch (IOException e1) {
                e1.printStackTrace();
                throw new BizException("全文检索未创建索引！");
            }
            throw new BizException("全文检索未创建索引！");
        }
        try {
            indexReader.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            throw new BizException("全文检索未创建索引！");
        }
        return page;
    }

    /**
     * 重检索引
     */
    @Override
    public void reloadIndex(List<IndexObject> list) {
        deleteAll();
        for (IndexObject indexObj : list) {
            create(indexObj);
        }
    }


    /**
     * 根据页码和分页大小获取上一次最后一个ScoreDoc
     *
     * @param pageIndex
     * @param pageSize
     * @param query
     * @param indexSearcher
     * @return
     * @throws IOException
     */
    private ScoreDoc getLastScoreDoc(Integer pageNumber, Integer pageSize, Query query, IndexSearcher searcher)
            throws IOException {
        if (pageNumber == 1)
            return null;
        int total = pageSize * (pageNumber - 1);
        TopDocs topDocs = searcher.search(query, total);
        return topDocs.scoreDocs[total - 1];
    }

    /**
     * 高亮设置
     * @param query
     * @param doc
     * @param field
     * @return
     */
    private Highlighter addStringHighlighter(Query query) {
        QueryScorer scorer = new QueryScorer(query);
        Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, scorer);
        highlighter.setTextFragmenter(fragmenter);
        return highlighter;
    }

    /**
     * 获取Query 对象
     * 
     * @param keyword
     * @param module
     * @return
     */
    private Query getQuery(String keyword,String siteId) throws ParseException {
        try {
            //BooleanClause.Occur.MUST表示and, feedom.net 
            //BooleanClause.Occur.MUST_NOT表示not, 54com.cn 
            //BooleanClause.Occur.SHOULD表示or. 
            // TermQuery 精确查询
            // TermRangeQuery 查询一个范围
            // PrefixQuery 前缀匹配查询
            // WildcardQuery 通配符查询
            // BooleanQuery 多条件查询
            // PhraseQuery 短语查询
            // FuzzyQuery 模糊查询
            // Queryparser 万能查询（上面的都可以用这个来查询到）
            BooleanQuery booleanClauses = new BooleanQuery();
            booleanClauses.add(new TermQuery(new Term("siteId",siteId)),BooleanClause.Occur.MUST);
            if(CommonUtil.isNotEmpty(keyword)){
                QueryParser queryParser1 = new QueryParser(Version.LUCENE_47, "title", analyzer);
                Query termQuery1 = queryParser1.parse(keyword+"*");
                
                QueryParser queryParser2 = new QueryParser(Version.LUCENE_47, "keywords", analyzer);
                Query termQuery2 = queryParser2.parse(keyword+"*");
                
                QueryParser queryParser3 = new QueryParser(Version.LUCENE_47, "description", analyzer);
                Query termQuery3 = queryParser3.parse(keyword+"*");
                
                BooleanQuery q1 = new BooleanQuery();
                q1.add(new BooleanClause(termQuery1, BooleanClause.Occur.SHOULD));
                q1.add(new BooleanClause(termQuery2, BooleanClause.Occur.SHOULD));
                q1.add(new BooleanClause(termQuery3, BooleanClause.Occur.SHOULD));
                booleanClauses.add(q1,BooleanClause.Occur.MUST);
            }

            return booleanClauses;
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

}
